using System;
using ch.ethz.ssh2.util;
namespace ch.ethz.ssh2.crypto.cipher
{
	
	/// <summary> CipherInputStream.
	/// 
	/// </summary>
	/// <author>  Christian Plattner
	/// </author>
	/// <version>  2.50, 03/15/10
	/// </version>
	public class CipherInputStream
	{
		private void  InitBlock()
		{
			input_buffer = new byte[BUFF_SIZE];
		}
		internal BlockCipher currentCipher;
		internal System.IO.Stream bi;
		internal byte[] buffer;
		internal byte[] enc;
		internal int blockSize;
		internal int pos;
		
		/*
		* We cannot use java.io.BufferedInputStream, since that is not available in
		* J2ME. Everything could be improved alot here.
		*/
		
		//UPGRADE_NOTE: Final was removed from the declaration of 'BUFF_SIZE '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
		internal int BUFF_SIZE = 2048;
		//UPGRADE_NOTE: The initialization of  'input_buffer' was moved to method 'InitBlock'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'"
		internal byte[] input_buffer;
		internal int input_buffer_pos = 0;
		internal int input_buffer_size = 0;
		
		public CipherInputStream(BlockCipher tc, System.IO.Stream bi)
		{
			InitBlock();
			this.bi = bi;
			changeCipher(tc);
		}
		
		private int fill_buffer()
		{
			input_buffer_pos = 0;
			input_buffer_size = (bi is ch.ethz.ssh2.StreamGobbler || bi is ch.ethz.ssh2.channel.ChannelInputStream)?(int) Utility.InvokeMethodAsVirtual(bi, "read", new System.Object[]{input_buffer, 0, BUFF_SIZE}):Utility.ReadInput(bi, input_buffer, 0, BUFF_SIZE);
			return input_buffer_size;
		}
		
		private int internal_read(byte[] b, int off, int len)
		{
			if (input_buffer_size < 0)
				return - 1;
			
			if (input_buffer_pos >= input_buffer_size)
			{
				if (fill_buffer() <= 0)
					return - 1;
			}
			
			int avail = input_buffer_size - input_buffer_pos;
			int thiscopy = (len > avail)?avail:len;
			
			Array.Copy(input_buffer, input_buffer_pos, b, off, thiscopy);
			input_buffer_pos += thiscopy;
			
			return thiscopy;
		}
		
		public virtual void  changeCipher(BlockCipher bc)
		{
			this.currentCipher = bc;
			blockSize = bc.BlockSize;
			buffer = new byte[blockSize];
			enc = new byte[blockSize];
			pos = blockSize;
		}
		
		private void  getBlock()
		{
			int n = 0;
			while (n < blockSize)
			{
				int len = internal_read(enc, n, blockSize - n);
				if (len < 0)
					throw new System.IO.IOException("Cannot read full block, EOF reached.");
				n += len;
			}
			
			try
			{
				currentCipher.transformBlock(enc, 0, buffer, 0);
			}
			catch (System.Exception e)
			{
				throw new System.IO.IOException("Error while decrypting block.");
			}
			pos = 0;
		}
		
		public virtual int read(byte[] dst)
		{
			return read(dst, 0, dst.Length);
		}
		
		public virtual int read(byte[] dst, int off, int len)
		{
			int count = 0;
			
			while (len > 0)
			{
				if (pos >= blockSize)
					getBlock();
				
				int avail = blockSize - pos;
				int copy = System.Math.Min(avail, len);
				Array.Copy(buffer, pos, dst, off, copy);
				pos += copy;
				off += copy;
				len -= copy;
				count += copy;
			}
			return count;
		}
		
		public virtual int read()
		{
			if (pos >= blockSize)
			{
				getBlock();
			}
			return buffer[pos++] & 0xff;
		}
		
		public virtual int readPlain(byte[] b, int off, int len)
		{
			if (pos != blockSize)
				throw new System.IO.IOException("Cannot read plain since crypto buffer is not aligned.");
			int n = 0;
			while (n < len)
			{
				int cnt = internal_read(b, off + n, len - n);
				if (cnt < 0)
					throw new System.IO.IOException("Cannot fill buffer, EOF reached.");
				n += cnt;
			}
			return n;
		}
	}
}